import {
  ACESFilmicToneMapping,
  OrthographicCamera,
  Scene,
  SRGBColorSpace,
  WebGLRenderer,
} from 'three'
import { OrbitControls } from 'three/addons'
import { VideoBg } from './videoBg'
import { GetCameraStream } from '@/utils/camera'
import { FaceMesh } from './mesh'
import { Glasses } from './glasses'
import type { NormalizedLandmark } from '@mediapipe/tasks-vision'
import { Environment } from './Environment'
import { Contrast } from '@/scene/contrast'
import type { Options } from '@/try-on/config'
import type { ZoomResult } from '@type/index'
import { Debug } from './Debug'
import Stats from 'three/addons/libs/stats.module.js'
export class SceneManager {
  #canvas: HTMLCanvasElement
  scene: Scene
  #contorl!: OrbitControls
  #renderer: WebGLRenderer
  #camera!: OrthographicCamera
  #enableControl: boolean
  #video!: GetCameraStream
  videoElement = document.createElement('video')
  background!: VideoBg
  #canvasWidth = 0
  #faceMesh!: FaceMesh
  #canvasHeight = 0
  glasses?: Glasses
  #environment?: Environment
  #options: Options
  contrast?: Contrast
  enlargeDisable: boolean
  shrinkDisable: boolean
  #stats?: Stats
  debug?: Debug
  constructor(canvas: HTMLCanvasElement, options: Options) {
    this.#options = options

    this.#canvas = canvas
    this.scene = new Scene()
    this.#enableControl = options.three.control
    this.#renderer = new WebGLRenderer({
      canvas,
      antialias: true,
    })
    this.#renderer.setPixelRatio(window.devicePixelRatio || 1)
    this.#renderer.outputColorSpace = SRGBColorSpace
    this.#renderer.toneMapping = ACESFilmicToneMapping
    this.#renderer.toneMappingExposure = 1
    this.#renderer.localClippingEnabled = true
    this.#buildCamera()
    this.enlargeDisable = options.three.cameraZoom.max >= this.#camera.zoom
    this.shrinkDisable = options.three.cameraZoom.min <= this.#camera.zoom
    this.#buildControl()
    this.#buildVideo()
    this.#buildContrast()
    this.#bildMesh()
    this.#buildGlasses()
    this.#buildEnvironment()
    this.#buildBackground()
    this.#buildStats()
    this.#buildDebug()
  }
  #buildDebug() {
    this.debug = new Debug(
      this.#options,
      this.#canvas.parentElement!,
      this.#contorl,
      this.#camera,
      this.contrast!,
      this,
    )
  }
  #buildStats() {
    this.#stats = new Stats()
    this.#stats.showPanel(0)
    this.#canvas.parentElement?.appendChild(this.#stats.dom)
  }
  #buildCamera() {
    this.#camera = new OrthographicCamera(
      -this.#renderer.domElement.width / 2,
      this.#renderer.domElement.width / 2,
      this.#renderer.domElement.height / 2,
      -this.#renderer.domElement.height / 2,
      -2000,
      2000,
    )
    this.#camera.position.z = 1
  }
  #buildContrast() {
    this.contrast = new Contrast(this.#canvas, this.#camera)
  }
  #buildControl() {
    this.#contorl = new OrbitControls(this.#camera!, this.#renderer.domElement)
  }
  #buildBackground() {
    this.background = new VideoBg(
      this.scene,
      this.#canvasWidth,
      this.#canvasHeight,
      this.videoElement,
      this.#options,
    )
  }
  #buildGlasses() {
    this.glasses = new Glasses(this.scene, this.#canvas.width, this.#canvas.height, this.contrast!)
  }
  #buildVideo() {
    this.#video = new GetCameraStream(this.videoElement, this.#options)
  }
  #bildMesh() {
    this.#faceMesh = new FaceMesh(this.scene, this.#canvasWidth, this.#canvasHeight, this.#options)
  }
  #buildEnvironment() {
    this.#environment = new Environment(this.scene, this.#renderer)
  }
  animate() {
    this.#stats?.begin()
    this.background?.update()
    if (this.#enableControl) this.#contorl?.update()
    if (this.#resizeRendererToDisplaySize()) {
      this.#faceMesh?.updateDimensions(this.#canvasWidth, this.#canvasHeight)
      this.glasses?.updateDimensions(this.#canvasWidth, this.#canvasHeight)
      this.background?.updateDimensions(this.#canvasWidth, this.#canvasHeight)
      this.#updateCramera()
    }
    this.glasses?.update()
    this.#faceMesh?.update()
    this.#renderer.render(this.scene, this.#camera)
    this.#stats?.end()
  }
  #resizeRendererToDisplaySize() {
    const canvas = this.#canvas
    const width = canvas.getBoundingClientRect().width
    const aspect = this.#options.tryOn.sceneAspect
    if ((this.#canvasWidth != width && width) || aspect == 'full') {
      let sceneAspect = 1
      if (aspect == 'full') {
        sceneAspect = window.innerWidth / window.innerHeight
      } else {
        sceneAspect = aspect
      }
      const height = Math.round(width / sceneAspect)
      this.#renderer.setSize(width, height, false)
      this.#canvasWidth = width
      this.#canvasHeight = height
      return true
    }
  }
  #updateCramera() {
    this.#camera.top = this.#canvasHeight / 2
    this.#camera.bottom = -this.#canvasHeight / 2
    this.#camera.left = -this.#canvasWidth / 2
    this.#camera.right = this.#canvasWidth / 2
    this.#camera.updateProjectionMatrix()
  }
  #getROI(videoElement: HTMLVideoElement) {
    const videoWidth = videoElement.videoWidth
    const videoHeight = videoElement.videoHeight
    const aspect = this.#options.tryOn.sceneAspect
    let targetAspect = 1
    if (aspect == 'full') {
      targetAspect = innerWidth / innerHeight
    } else {
      targetAspect = aspect
    }
    const videoAspect = videoWidth / videoHeight

    const xCenter = 0.5
    const yCenter = 0.5

    let width: number, height: number
    if (videoAspect > targetAspect) {
      height = 1
      width = targetAspect * (videoHeight / videoWidth)
    } else {
      width = 1
      height = videoWidth / videoHeight / targetAspect
    }
    return {
      regionOfInterest: {
        xCenter,
        yCenter,
        width,
        height,
      },
      rotationDegrees: 0,
    }
  }
  onLandmarks(landmarks: NormalizedLandmark[]) {
    this.glasses?.updateLandmarks(landmarks)
    this.#faceMesh.updateLandmarks(landmarks)
  }
  enlargeCamera(): ZoomResult {
    const result = {
      ...this.#options.three.cameraZoom,
      enlargeDisable: this.#camera.zoom >= this.#options.three.cameraZoom.max,
      shrinkDisable: this.#camera.zoom <= this.#options.three.cameraZoom.min,
    }
    if (this.#camera.zoom >= this.#options.three.cameraZoom.max) return result
    this.#camera.zoom += 0.1
    this.#camera.updateProjectionMatrix()
    return {
      ...this.#options.three.cameraZoom,
      enlargeDisable: this.#camera.zoom >= this.#options.three.cameraZoom.max,
      shrinkDisable: this.#camera.zoom <= this.#options.three.cameraZoom.min,
    }
  }
  shrinkCamera(): ZoomResult {
    if (this.#camera.zoom <= this.#options.three.cameraZoom.min)
      return {
        ...this.#options.three.cameraZoom,
        enlargeDisable: this.#camera.zoom >= this.#options.three.cameraZoom.max,
        shrinkDisable: this.#camera.zoom <= this.#options.three.cameraZoom.min,
      }
    this.#camera.zoom -= 0.1
    this.#camera.updateProjectionMatrix()
    return {
      ...this.#options.three.cameraZoom,
      enlargeDisable: this.#camera.zoom >= this.#options.three.cameraZoom.max,
      shrinkDisable: this.#camera.zoom <= this.#options.three.cameraZoom.min,
    }
  }
}
